In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.ticker as ticker
import matplotlib.dates as mdates
In [2]:
def add_bar_labels(ax, formatter, spacing = 3 ):
    for rect in ax.patches:
        y_value = rect.get_height()
        x_value = rect.get_x() + rect.get_width() / 2
        space = spacing
        va = 'bottom'
        if y_value <0:
            space *= -1
            va = 'top'
        label = formatter.format(y_value)
        ax.annotate(label, 
                    (x_value, y_value),
                    xytext=(0, space),
                    textcoords = "offset points",
                    ha="center",
                    va=va,
                   fontsize = 10,
                   color = "tab:gray")
In [3]:
factornames = ["Excess\nMarket", "Size", "Value-B2M", "Value-E2P", "Profitability", "Investment", "Momentum", "Reversal",
             "Beta", "Volume",  "Volatility"]
factorcodes = ["mkt", 'size', 'bm', 'ep','roe', 'ag', 'm12', 'm1','beta','idvc', 'dtvm']
path = r'.\sector-rotation-data'
In [4]:
def read_factor(code, filename = r".\input\factor\ten_factor_vw_{}_5.csv"):
    data = pd.read_csv(filename.format(code), index_col=0)
    data.index = pd.to_datetime(data.index, format="%Y-%m")
    data["mkt"] = data["market"] - data["rf"]
    return(data)
In [5]:
def factor_sum_plot(data, factorcodes, factornames, fig_title, fig_path, periods = 12, horizons = (1, 3, 1), **kwargs):
#     ann_ret = lambda x: (x+1).prod()**(periods/len(x)) - 1
    ann_ret = lambda x: np.mean(x)*periods
    ann_vol = lambda x: x.std()*(periods**0.5)
    sr = lambda x: ann_ret(x)/ann_vol(x)
    
    # set grid layout
    plt.style.use('seaborn')
    fig = plt.figure(num= 1, figsize=(24,12)) #gridspec_kw = {'height_ratios':[1, 4]}, 
    gs = gridspec.GridSpec(2,2, height_ratios = [1,1], width_ratios = [1, 1.4])
    ax1 = fig.add_subplot(gs[0,0])
    ax2 = fig.add_subplot(gs[1,0])
    ax3 = fig.add_subplot(gs[0,1])
    ax4 = fig.add_subplot(gs[1,1])
    
    h1, h2, h3 = horizons    
    # upper left: annualized return in the past 12 months (52 weeks) and past 36 months (156 weeks)
    pd.concat([data[factorcodes].iloc[-h1*periods:,:].apply(ann_ret).rename("last 12 months"),
               data[factorcodes].iloc[-h2*periods:,:].apply(ann_ret).rename("last 36 months")]
              , axis=1).T.plot.bar(ax = ax1, rot=1, width=0.8, colormap="Paired", fontsize = 12)
    add_bar_labels(ax1, "{:.1%}")
    ax1.legend(factornames, ncol=1, loc="center left", bbox_to_anchor = (1, 0.5), fontsize = 12)
    xtick1 = [" - ".join([data.index[-h1*periods].strftime("%Y/%m"),data.index[-1].strftime("%Y/%m")])]
    xtick1.append(" - ".join([data.index[-h2*periods].strftime("%Y/%m"),data.index[-1].strftime("%Y/%m")]))
    ax1.set_xticklabels(xtick1)
    ax1.set_title("Annualized Return", size = 16)

    # bottom left: annualized sharpe ratio in the past 12 months (52 weeks) and past 36 months (156 weeks)
    pd.concat([data[factorcodes].iloc[-h1*periods:,:].apply(sr).rename("last year"),
               data[factorcodes].iloc[-h2*periods:,:].apply(sr).rename("last 3 years")]
              , axis=1).T.plot.bar(ax = ax2, rot=1, width=0.8, colormap="Paired", fontsize = 12)
    add_bar_labels(ax2, "{:.2f}")
    ax2.legend(factornames, ncol=1, loc="center left", bbox_to_anchor = (1, 0.5),  fontsize = 12)
    xtick2 = [" - ".join([data.index[-h1*periods].strftime("%Y/%m"),data.index[-1].strftime("%Y/%m")])]
    xtick2.append(" - ".join([data.index[-h2*periods].strftime("%Y/%m"),data.index[-1].strftime("%Y/%m")]))
    ax2.set_xticklabels(xtick2)
    ax2.set_title("Annualized Sharpe Ratio", size = 16)

    # upper right: cumulative return in the past 12 months or 52 weeks
    cum_ret = data[factorcodes].iloc[-h3*periods-1:,:]+1
    cum_ret.iloc[0, :] = np.ones(len(factorcodes))
    tick3 = " - ".join([cum_ret.index[-h3*periods].strftime("%Y/%m"),cum_ret.index[-1].strftime("%Y/%m")])
    cum_ret.index = mdates.date2num(cum_ret.index)
    cum_ret.cumprod().plot(ax = ax3, colormap = "Paired", fontsize = 14, grid=True, legend = None, **kwargs)
    ax3.set_xlabel("")
    ax3.set_xlim([data.index[-h3*periods]-pd.Timedelta(days=20),data.index[-1]+pd.Timedelta(days=20)])
    ax3.xaxis.set_major_locator(mdates.MonthLocator(bymonth = (6,12) if h3 >=2 else (3,9,6,12)))
    ax3.set_title("Cumulative Return {}".format(tick3), size = 16)
    ax3.yaxis.set_ticks_position('right')
#     ax3.legend(factornames, ncol=1, loc="upper left", fontsize = 12)
    
#     for i in range(len(factorcodes)):
#         height = (data[factorcodes].iloc[-5*periods:,:]+1).prod()[i]
#         text = "{:.1%}".format(height)
#         ax3.annotate(xy=[data.index[-1]+pd.Timedelta(days=30), height], xytext = (1,0), 
#                      textcoords = 'offset points', text = text, color="tab:grey", fontsize = 12)
    
    cum_ret_roll = (data[factorcodes]+1).rolling(window = periods).apply(np.prod) - 1
    cum_ret_roll = cum_ret_roll.iloc[-h3*periods+1:,:]
    tick4 = " - ".join([cum_ret_roll.index[0].strftime("%Y/%m"),cum_ret_roll.index[-1].strftime("%Y/%m")])
    cum_ret_roll.index = mdates.date2num(cum_ret_roll.index)
    cum_ret_roll.plot(ax = ax4, colormap = "Paired", fontsize = 14, grid=True, legend = None, **kwargs)
    ax4.set_xlabel("")
    ax4.set_xlim([data.index[-h3*periods+1]-pd.Timedelta(days=20),data.index[-1]+pd.Timedelta(days=20)])
    ax4.xaxis.set_major_locator(mdates.MonthLocator(bymonth = (6,12) if h3 >=2 else (3,9,6,12)))
    ax4.set_title("Rolling 52-week Return {}".format(tick4), size = 16)
    ax4.yaxis.set_ticks_position('right')
    
    fig.suptitle(fig_title, fontsize = 18)
    fig.tight_layout(rect = [0, 0.03, 1, 0.98])
    plt.savefig(fig_path)
    plt.show()
In [6]:
code = "hk"
data = read_factor(code)
factor_sum_plot(data, factorcodes, factornames,  "Factor Performance Summary (Hong Kong)", "factor_summary_{}.png".format(code))
In [7]:
code = "us"
data = read_factor(code)
factor_sum_plot(data, factorcodes, factornames,  "Factor Performance Summary (U.S.)", "factor_summary_{}.png".format(code))
In [8]:
code = "cn"
data = read_factor(code)
display(data.describe())
factor_sum_plot(data,  factorcodes, factornames, "Factor Performance Summary (Shanghai-Shenzhen.)", "factor_summary_{}.png".format(code))
size idvc beta bm m12 m1 roe ag dtvm ep market rf mkt
count 266.000000 266.000000 266.000000 266.000000 266.000000 266.000000 266.000000 266.000000 266.000000 266.000000 266.000000 266.000000 266.000000
mean 0.012132 0.007767 -0.001913 0.003581 0.002173 0.005642 0.006235 0.003983 0.013794 0.008853 0.008623 0.001881 0.006742
std 0.072249 0.059669 0.059206 0.057077 0.057326 0.055125 0.057137 0.040892 0.060475 0.058804 0.077392 0.000585 0.077433
min -0.356786 -0.195979 -0.157931 -0.256189 -0.287557 -0.296507 -0.170075 -0.149548 -0.305313 -0.204325 -0.266088 0.001241 -0.269474
25% -0.026329 -0.025911 -0.039261 -0.026778 -0.027483 -0.021495 -0.028258 -0.019108 -0.017210 -0.025798 -0.040618 0.001241 -0.042147
50% 0.012597 0.008026 -0.004233 0.003777 0.001018 0.006661 0.003550 0.004835 0.011125 0.009787 0.010305 0.001856 0.009007
75% 0.049828 0.039312 0.035139 0.033030 0.036188 0.034251 0.035183 0.025644 0.042430 0.042739 0.043826 0.002263 0.041681
max 0.357761 0.208310 0.206149 0.322304 0.177692 0.195060 0.236803 0.141606 0.328830 0.285424 0.304778 0.003386 0.302482
In [9]:
data = read_portfolio(4)
factor_sum_plot(data, codes, names, "Sector Allocation Performance ($\gamma = 4$)", "sector_summary_4.png")
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
C:\Users\ARIZON~1\AppData\Local\Temp/ipykernel_16176/1601687472.py in <module>
----> 1 data = read_portfolio(4)
      2 factor_sum_plot(data, codes, names, "Sector Allocation Performance ($\gamma = 4$)", "sector_summary_4.png")

NameError: name 'read_portfolio' is not defined
In [ ]:
data = read_portfolio(8)
factor_sum_plot(data, codes, names, "Sector Allocation Performance ($\gamma = 8$)", r".\plots\sector_summary_8.png")

Weekly Factor Return¶

In [7]:
data = read_factor_weekly("cn800", format = "%Y-%m-%d")
factor_sum_plot(data, factorcodes, factornames, "Monthly Factor Performance Summary (Shanghai-Shenzhen 800)",r".\plots\factor_cn800_5.png",  periods = 52)
In [8]:
data = read_factor_weekly("hk300", format = "%Y-%m-%d")
factor_sum_plot(data, factorcodes, factornames, "Monthly Factor Performance Summary (Hong Kong Top 300)", r".\plots\factor_hk300.png", periods = 52)
In [9]:
data = read_factor_weekly("us1500", format = "%Y-%m-%d")
factor_sum_plot(data, factorcodes, factornames, "Monthly Factor Performance Summary (U.S. Top 1500)", r".\plots\factor_us1500_5.png", periods = 52)

Weekly Factor Return (Long & Short)¶

In [15]:
factor_sum_plot(long, factorcode1, factorname1, "Monthly Factor Performance Summary (Hong Kong 300 - Long)", 
                r".\plots\factor_{}_long_5".format(code), periods = 52)
In [16]:
factor_sum_plot(short, factorcode1, factorname1, "Monthly Factor Performance Summary (Hong Kong 300 - Short)", 
                r".\plots\factor_{}_short_5".format(code), periods = 52)
In [17]:
factor_sum_plot(long, factorcode1, factorname1, "Monthly Factor Performance Summary (Shanghai-Shenzhen 800 - Long)",r".\plots\factor_{}_long_5".format(code), periods = 52)
In [18]:
factor_sum_plot(short, factorcode1, factorname1, "Monthly Factor Performance Summary (Shanghai-Shenzhen 800 - Short)",r".\plots\factor_{}_short_5".format(code), periods = 52)
In [22]:
factor_sum_plot(long, factorcode1, factorname1, "Monthly Factor Performance Summary (U.S. 1500 - Long)",r".\plots\factor_{}_long_5".format(code), periods = 52)
In [23]:
factor_sum_plot(short, factorcode1, factorname1, "Monthly Factor Performance Summary (U.S. 1500 - Short)",r".\plots\factor_{}_short_5".format(code), periods = 52)

Portfolio Performance¶

In [27]:
factor_sum_plot(data, data.columns, data.columns, "Sector Rotation Performance (Shanghai-Shenzhen)",
                r".\plots\performance_{}_5.png".format(code), periods = 52, horizons = (1, 5, 5))
In [29]:
factor_sum_plot(df_all, df_all.columns,  df_all.columns, "Sector Rotation Performance (Hong Kong)", 
                r".\plots\performance_{}_5.png".format(code), periods = 52, horizons = (1, 5, 5))
In [31]:
factor_sum_plot(df_all, df_all.columns,  df_all.columns, "Sector Rotation Performance (Hong Kong)", 
                r".\plots\performance_{}_5.png".format(code), periods = 52, horizons = (1, 5, 5))
In [ ]:
data = pd.read_csv(
    r".\sector-rotation-data\hsci_sectors_monthly.csv", index_col=0)
data.index = pd.to_datetime(data.index)
factor_sum_plot(data, data.columns, data.columns,
                "Sector Performance (Hong Kong)", "hk_sum.png")